package com.universalInterface.common.exception;

import com.universalInterface.common.result.ApiCode;
import com.universalInterface.common.result.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.servlet.http.HttpServletRequest;
import java.nio.file.AccessDeniedException;
import java.sql.SQLException;
import java.util.List;

/**
 * @author Lx
 * @date 2023/12/8 15:43
 * @description 全局异常捕捉处理器
 * <br/> @ResponseStatus：原本浏览器是显示异常所对应的状态码，通过此注解可以更改返回的状态码
 * <br/> @ExceptionHandler：处理基类及子类的异常
 */
@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {

    /**
     * BaseException ：基类-自定义异常类
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = BaseException.class)
    protected ApiResult baseException(HttpServletRequest request, BaseException e) {
        return handleException(request, e, e.getCode(), e.getMessage());
    }

    /**
     * Exception：异常
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    protected ApiResult exception(HttpServletRequest request, Exception e) {
        return handleException(request, e, ApiCode.INTERNAL_SERVER_ERROR);
    }

    /**
     * NullPointerException：空指针异常
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = NullPointerException.class)
    protected ApiResult nullPointerException(HttpServletRequest request, Exception e) {
        return handleException(request, e, HttpStatus.INTERNAL_SERVER_ERROR.value(), "空指针异常");
    }

    /**
     * 统一处理请求参数校验(实体对象传参)
     * BindException：绑定异常
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = BindException.class)
    protected ApiResult bindException(HttpServletRequest request, BindException e) {
        StringBuilder message = new StringBuilder();
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        for (FieldError error : fieldErrors) {
            message.append(error.getField()).append(error.getDefaultMessage()).append(",");
        }
        message = new StringBuilder(message.substring(0, message.length() - 1));
        return handleException(request, e, ApiCode.PARAMETER_PARSE_EXCEPTION.getCode(), message.toString());
    }

    /**
     * SQLException：SQL异常
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = SQLException.class)
    protected ApiResult sqlException(HttpServletRequest request, SQLException ex) {
        return handleException(request, ex, HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务运行SQLException异常");
    }

    /**
     * IllegalArgumentException：非法参数异常
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({IllegalArgumentException.class})
    protected ApiResult illegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
        return handleException(request, e, HttpStatus.INTERNAL_SERVER_ERROR.value(), "非法参数异常");
    }

    /**
     * MethodArgumentTypeMismatchException：方法参数类型不匹配异常
     */
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    protected ApiResult methodArgumentTypeMismatchException(HttpServletRequest request, MethodArgumentTypeMismatchException e) {
        return handleException(request, e, ApiCode.METHOD_ARGUMENT_TYPE_MISMATCH_EXCEPTION);
    }

    /**
     * 统一处理请求参数校验(json)
     * MethodArgumentNotValidException：方法参数无效异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    protected ApiResult methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
        StringBuilder message = new StringBuilder();
        for (FieldError error : e.getBindingResult().getFieldErrors()) {
            message.append(error.getDefaultMessage()).append(",");
        }
        message = new StringBuilder(message.substring(0, message.length() - 1));
        return handleException(request, e, ApiCode.PARAMETER_PARSE_EXCEPTION.getCode(), message.toString());
    }

    /**
     * 统一处理请求参数校验(普通传参)
     * ConstraintViolationException：约束违反异常
     */
    // @ExceptionHandler(value = ConstraintViolationException.class)
    // protected ApiResult constraintViolationException(HttpServletRequest request, ConstraintViolationException e) {
    //     StringBuilder message = new StringBuilder();
    //     Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
    //     for (ConstraintViolation<?> violation : violations) {
    //         Path path = violation.getPropertyPath();
    //         String[] pathArr = StringUtil.splitByWholeSeparatorPreserveAllTokens(path.toString(), ".");
    //         message.append(pathArr[1]).append(violation.getMessage()).append(",");
    //     }
    //     message = new StringBuilder(message.substring(0, message.length() - 1));
    //     return handleException(request, e, ApiCode.PARAMETER_PARSE_EXCEPTION.getCode(), message.toString());
    // }

    /**
     * HttpRequestMethodNotSupportedException：Http请求方法不支持异常
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    protected ApiResult httpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException e) {
        return handleException(request, e, HttpStatus.METHOD_NOT_ALLOWED.value(), "Http请求方法不支持异常");
    }

    /**
     * HttpMediaTypeNotSupportedException：Http媒体类型不支持异常
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
    protected ApiResult httpMediaTypeNotSupportedException(HttpServletRequest request, HttpMediaTypeNotSupportedException e) {
        return handleException(request, e, HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "Http媒体类型不支持异常");
    }

    /**
     * HttpMessageNotReadableException：Http消息不可读异常 / Http请求参数解析异常
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    protected ApiResult httpMessageNotReadableException(HttpServletRequest request, HttpMessageNotReadableException e) {
        return handleException(request, e, ApiCode.PARAMETER_EXCEPTION.getCode(), "请求参数解析异常");
    }

    /**
     * HttpMessageConversionException：Http消息转换异常 / 序列化异常,检查请求参数
     */
    @ExceptionHandler(value = HttpMessageConversionException.class)
    protected ApiResult httpMessageConversionException(HttpServletRequest request, HttpMessageConversionException e) {
        return handleException(request, e, ApiCode.HTTP_MESSAGE_CONVERSION_EXCEPTION.getCode(), "序列化异常,检查请求参数");
    }

    /**
     * AccessDeniedException：访问被拒绝异常 / 没有权限访问异常
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(value = AccessDeniedException.class)
    protected ApiResult accessDeniedException(HttpServletRequest request, AccessDeniedException e) {
        return handleException(request, e, HttpStatus.UNAUTHORIZED.value(), "没有权限访问异常");
    }

    /**
     * MissingServletRequestParameterException : 缺少Servlet请求参数异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    protected ApiResult missingServletRequestParameterException(HttpServletRequest request, MissingServletRequestParameterException e) {
        return handleException(request, e, HttpStatus.BAD_REQUEST.value(), "缺少请求参数:" + e.getParameterName());
    }

    /**
     * MissingServletRequestPartException：缺少Servlet请求部分异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestPartException.class)
    protected ApiResult<Boolean> missingServletRequestPartException(HttpServletRequest request, MissingServletRequestPartException e) {
        return handleException(request, e, HttpStatus.BAD_REQUEST.value(), "请求错误，请修正请求");
    }


    /**
     * @param request http请求对象
     * @param e       异常对象
     * @param apiCode 枚举类ApiCode
     * @return
     */
    protected static ApiResult handleException(HttpServletRequest request, Exception e, ApiCode apiCode) {
        return handleException(request, e, apiCode.getCode(), apiCode.getMessage());
    }

    /**
     * 优先级：自定义消息 > apiCode.getMessage() > e.getMessage()
     *
     * @param request http请求对象
     * @param e       异常对象
     * @param code    状态码 code
     * @param message 状态码描述消息（比如：在写参数异常消息时，要求显示错误的参数名字，这时就要使用自定义消息）
     * @return
     */
    protected static ApiResult handleException(HttpServletRequest request, Exception e, Integer code, String message) {
        Integer c = null;
        String msg = null;
        if (code == null) {
            c = 500;
        } else {
            c = code;
        }
        if (!isBlank(message)) {
            msg = message;
        } else if (!isBlank(e.getMessage())) {
            msg = e.getMessage();
        } else {
            msg = "服务器内部异常";
        }
        log.error("路径URI:{} ; 状态码:{} ; 异常描述:{} ; 异常堆栈:", request.getRequestURI(), c, msg, e);
        return ApiResult.error(c, msg);
    }

    /**
     * <p>apache的commons-xxx.jar的系列产品（比如：commons-lang3.jar）是根据jdk1.8的技术来写的
     * <p>StringUtils.isBlank(null)      = true
     * <p>StringUtils.isBlank("")        = true
     * <p>StringUtils.isBlank(" ")       = true
     * <p>StringUtils.isBlank("bob")     = false
     * <p>StringUtils.isBlank("  bob  ") = false
     */
    private static boolean isBlank(CharSequence cs) {
        int strLen;
        if (cs != null && (strLen = cs.length()) != 0) {
            for (int i = 0; i < strLen; ++i) {
                if (!Character.isWhitespace(cs.charAt(i))) {
                    return false;
                }
            }
            return true;
        } else {
            return true;
        }
    }


}
